package de.westranger.forex.code.generation;

import java.text.ParseException;
import java.util.Map;
import java.util.Stack;
import java.util.TreeMap;

import de.westranger.forex.trading.genetic.allel.Node;
import de.westranger.forex.trading.genetic.allel.operator.CompareOperator;
import de.westranger.forex.trading.genetic.allel.operator.EvaluateTerminal;
import de.westranger.forex.trading.genetic.allel.operator.EvaluateTerminalSymbol;
import de.westranger.forex.trading.genetic.allel.operator.LogicalOperator;
import de.westranger.forex.trading.genetic.allel.operator.LogicalOperatorType;
import de.westranger.forex.trading.genetic.allel.operator.Terminal;
import de.westranger.forex.trading.genetic.allel.operator.CompareOperatorType;
import de.westranger.forex.trading.genetic.allel.operator.TerminalSymbol;

public final class ErshovAlgorithm {
	
	// http://cg.scs.carleton.ca/~morin/teaching/3002/notes/dataflow.pdf
	
	// http://www.javaworld.com/javaworld/jw-06-2006/jw-0612-dynamic.html?page=4
	
	private final Map<TerminalSymbol,String> termSymb;
	private final Map<EvaluateTerminalSymbol,String> evalTermSymb;
	private final Stack<String> instructions;
	private final Stack<String> variables;
	private int registerCounter;
	private final MQLIndicatorResolver mql;
	
	
	public ErshovAlgorithm(){
		this.termSymb = new TreeMap<TerminalSymbol,String>();
		this.evalTermSymb = new TreeMap<EvaluateTerminalSymbol,String>();
		this.instructions = new Stack<String>();
		this.variables = new Stack<String>();
		this.mql = new MQLIndicatorResolver();
		registerCounter = 0;
	}


	
	private String resolveOperator(final Node root, final String left, final String right) throws ParseException {
		String result = null;
		if(root instanceof CompareOperator){
			CompareOperator co = (CompareOperator) root;
			
			switch(CompareOperatorType.parse(co.getValue())){
			case LARGER: result = left+" > "+right;
				break;
			case LESS: result = left+" < "+right;
				break;
			default:
				break;
			}
		}else if(root instanceof LogicalOperator){
			LogicalOperator lo = (LogicalOperator) root;
			//System.out.println(root.getLeftChild().getClass().getSimpleName() + " " + root.getRightChild().getClass().getSimpleName());
			switch(LogicalOperatorType.parse(lo.getValue())){
			case AND:	result = left+" && "+right;
						break;	
			case NAND:	result = "!("+left+" && "+right+")";
						break;
			case NOR:	result = "!("+left + " || "+ right+")";
						break;
			case OR:	result = left + " || "+ right;
						break;
			case XNOR:	result = "("+left + " || !"+ right+") && (!"+left + " || "+ right+")";
						break;
			case XOR:	result = "("+left + " && !"+ right+") || (!"+left + " && "+ right+")";
						break;
			default:
						break;
		}			
		}else{
			throw new IllegalArgumentException("unknown node type");
		}
		
		return result;
	}


	public String evaluateTree(final Node root) throws ParseException{
	if(root != null){
		if(root instanceof Terminal){
			TerminalSymbol ts = TerminalSymbol.parse(root.getValue());
			if(!termSymb.containsKey(ts)){
				termSymb.put(ts, "R"+(registerCounter++));
			}
			return termSymb.get(ts);
		}else if(root instanceof EvaluateTerminal){
			
			EvaluateTerminalSymbol ts = EvaluateTerminalSymbol.parse(root.getValue());
			//System.out.println(ts.name());
			if(!evalTermSymb.containsKey(ts)){
				evalTermSymb.put(ts, "R"+(registerCounter++));
			}
			return evalTermSymb.get(ts);
		}else{
			// we are dealing with an operator
			String left = this.evaluateTree(root.getLeftChild());
			String right = this.evaluateTree(root.getRightChild());
			
			String result = null;
			//System.out.println(left +  " " + right);
			if(left.startsWith("R") && right.startsWith("R")){
				// new variable
				result = "V"+this.variables.size()+" = ("+this.resolveOperator(root,left,right)+");";
				this.variables.push("boolean V"+this.variables.size()+" = false;");
			}else if(!left.startsWith("R")){
				String var = left.split(" ")[0];
				result = var + " = ("+this.resolveOperator(root,var,right)+");";
			}else if(!right.startsWith("R")){
				String var = right.split(" ")[0];
				result = var + " = ("+this.resolveOperator(root,left,var)+");";
			}else{
				String varA = left.split(" ")[0];
				String varB = right.split(" ")[0];
				result = varA + " = ("+this.resolveOperator(root,varA,varB)+");";				
			}
			this.instructions.push(result);
			// "("+ this.resolveOperator(root,left,right)+")";
			return result.split(" ")[0];
		}
	}else{
		return "";
	}
}
	
	public void printVariables(){
		for(TerminalSymbol ts : this.termSymb.keySet()){
			System.out.println("\tdouble "+ this.termSymb.get(ts) + "\t= "+this.mql.resolveTerminal(ts, 0)+";\t//" + ts.name());
		}
		
		System.out.println();
		StringBuilder evalSb = new StringBuilder();
//		for(EvaluateTerminalSymbol ts : this.evalTermSymb.keySet()){
//			if(this.evalTermSymb.get(ts).length()+5 < 8){
//				evalSb.append("\tboolean "+ this.evalTermSymb.get(ts) + "\t\t= "+this.mql.resolveEvaluateTerminal(ts)+";\t//" + ts.name()+"\n");
//			}else{
//				evalSb.append("\tboolean "+ this.evalTermSymb.get(ts) + "\t= "+this.mql.resolveEvaluateTerminal(ts)+";\t//" + ts.name()+"\n");				
//			}
//		}

		String evalString = evalSb.toString();
//		for(TerminalSymbol ts : this.termSymb.keySet()){
//			
//			if(this.mql.resolveTerminal(ts, 0).trim().length() > 1){
//				evalString = evalString.replace(this.mql.resolveTerminal(ts, 0), this.termSymb.get(ts));			
//			}
//		}

		System.out.println(evalString);
		StringBuilder sb = new StringBuilder();
		sb.append('\t');
		sb.append("bool");
		sb.append(' ');
		for(String s: this.variables){
			sb.append(s.replace("bool ", "").replace(';', ','));
			sb.append(' ');
		}
		sb.delete(sb.length()-2, sb.length());
		sb.append(';');
		System.out.println(sb.toString());

		System.out.println();
		for(String s: this.instructions){
			System.out.println("\t"+s);
		}
		System.out.println("\treturn ("+this.instructions.peek().split(" ")[0]+");");
	}
	
}
